[genre].tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import clsx from "clsx";
  2. import Link from "next/link";
  3. import { FormEvent, useContext, useMemo } from "react";
  4. import { useRouter } from "next/router";
  5. import { GetServerSideProps } from "next";
  6. import { get } from "libs/http";
  7. import useGet from "libs/hooks/useGet";
  8. import { Context } from "libs/context";
  9. import NovelItem from "components/NovelItem";
  10. import styles from "styles/genre.module.scss";
  11. import useStore from "libs/hooks/useStore";
  12. import { SeoHead, SeoHeadConfig } from "components/SeoHead";
  13. import Pagination from "components/Pagination";
  14. import PaginationItem from "components/Pagination/PaginationItem";
  15. import SearchForm from "components/common/SearchForm";
  16. import EmptyResult from "components/EmptyResult";
  17. interface Query {
  18. genre?: string;
  19. page?: string;
  20. q?: string;
  21. }
  22. function getKey(query: Query = {}) {
  23. const queryString = `?page=${
  24. isNaN(Number(query.page)) ? "1" : query.page
  25. }&size=20`;
  26. return query.q
  27. ? `/api/search${queryString}${
  28. query.genre ? `&genre=${query.genre}` : ""
  29. }&name=${query.q}`
  30. : query.genre
  31. ? `/api/genre/${query.genre}${queryString}`
  32. : `/api/all${queryString}`;
  33. }
  34. const Genre = () => {
  35. const { query, asPath } = useRouter();
  36. const { genre, siteConfig } = useStore();
  37. const { data } = useGet<NovelList>(getKey(query));
  38. const currentName = useMemo(() => {
  39. const item = genre.find((item) => item.uri === query.genre);
  40. return item ? item.name : "All";
  41. }, [query.genre, genre]);
  42. const pashName = useMemo(() => {
  43. return asPath.split("?")[0];
  44. }, [asPath]);
  45. const seoConfig: SeoHeadConfig = useMemo(() => {
  46. // const genreName =
  47. return {
  48. title: `${currentName} Novels - ${siteConfig.siteName}`,
  49. description: `Explore ${currentName} Web Novels on NovelDit. Online Reading ${currentName} Web Novels for Free!`,
  50. keywords: `${[
  51. `${currentName} stories`,
  52. `${currentName} novels`,
  53. `read ${currentName} novels`,
  54. siteConfig.keywords,
  55. siteConfig.siteName,
  56. ].join(", ")}`,
  57. url: `https://${siteConfig.host}/novels${
  58. query.genre ? `/${query.genre}` : ""
  59. }`,
  60. canonical: `https://${siteConfig.host}/novels${
  61. query.genre ? `/${query.genre}` : ""
  62. }`,
  63. ...(data?.data.pageIndex && data.data.pageIndex > 1
  64. ? {
  65. pre: `https://${siteConfig.host}/novels${
  66. query.genre ? `/${query.genre}` : ""
  67. }${query.q ? `?q=${query.q}` : ""}${
  68. data.data.pageIndex - 1 === 1
  69. ? ""
  70. : `${query.q ? `&` : "?"}page=${data.data.pageIndex - 1}`
  71. }`,
  72. }
  73. : {}),
  74. ...(data?.data.pageSize &&
  75. data?.data.pageIndex &&
  76. data.data.pageSize > data.data.pageIndex
  77. ? {
  78. next: `https://${siteConfig.host}/novels${
  79. query.genre ? `/${query.genre}` : ""
  80. }${query.q ? `?q=${query.q}` : ""}${`${query.q ? `&` : "?"}page=${
  81. data.data.pageIndex + 1
  82. }`}`,
  83. }
  84. : {}),
  85. jsonLd: JSON.stringify([
  86. {
  87. "@context": "https://schema.org",
  88. "@type": "BreadcrumbList",
  89. itemListElement: [
  90. {
  91. "@type": "ListItem",
  92. position: 1,
  93. name: "Home",
  94. item: `https://${siteConfig.host}`,
  95. },
  96. {
  97. "@type": "ListItem",
  98. position: 2,
  99. name: "All Novels",
  100. item: `https://${siteConfig.host}/novels`,
  101. },
  102. ...(query.genre
  103. ? [
  104. {
  105. "@type": "ListItem",
  106. position: 3,
  107. name: `${currentName} Novels`,
  108. item: `https://${siteConfig.host}/novels/${query.genre}`,
  109. },
  110. ]
  111. : []),
  112. ],
  113. },
  114. {
  115. "@context": "https://schema.org",
  116. "@type": "ItemList",
  117. itemListElement: (data?.data.rows || []).map((item, idx) => ({
  118. "@type": "ListItem",
  119. position: idx + 1,
  120. url: `https://${siteConfig.host}/novel/${item.uri}`,
  121. name: item.name,
  122. image:
  123. "http://img.webnovel.com/bookcover/16709365405930105/600/600.jpg",
  124. author: { "@type": "Person", name: item.author },
  125. publisher: { "@type": "Organization", name: siteConfig.siteName },
  126. })),
  127. },
  128. ...siteConfig.jsonLd,
  129. ]),
  130. };
  131. }, [
  132. currentName,
  133. data?.data.rows,
  134. query.genre,
  135. siteConfig.host,
  136. siteConfig.jsonLd,
  137. siteConfig.keywords,
  138. siteConfig.siteName,
  139. ]);
  140. return (
  141. <main className="container">
  142. <SeoHead seoConfig={seoConfig} />
  143. <h2 className="novel-title">Genres</h2>
  144. <div className={styles.genres}>
  145. <Link
  146. href="/novels"
  147. title="All novels"
  148. className={clsx(styles.genre, {
  149. [styles.current]: !query.genre,
  150. })}
  151. >
  152. All
  153. </Link>
  154. {genre.map((item) => (
  155. <Link
  156. href={`/novels/${item.uri}`}
  157. key={item.uri}
  158. title={item.name}
  159. className={clsx(styles.genre, {
  160. [styles.current]: query.genre === item.uri,
  161. })}
  162. >
  163. {item.name}
  164. </Link>
  165. ))}
  166. </div>
  167. <h3 className="novel-title">Search</h3>
  168. <SearchForm />
  169. <h1 className="novel-title">{`${currentName} Novels`}</h1>
  170. {data?.data.rows && data.data.rows.length > 0 ? (
  171. <ul className="novel-list">
  172. {(data?.data.rows || []).map((item) => (
  173. <NovelItem
  174. key={item.uri}
  175. slug={item.uri}
  176. img={item.img}
  177. name={item.name}
  178. />
  179. ))}
  180. </ul>
  181. ) : (
  182. <EmptyResult
  183. icon="autoStories"
  184. title={`Couldn't find books${
  185. query.q ? ` matching "${query.q}"` : ""
  186. }.`}
  187. />
  188. )}
  189. {data?.data.total_pages && data.data.total_pages > 1 ? (
  190. <Pagination
  191. className="mt-5"
  192. count={data?.data.total_pages}
  193. page={data?.data.pageIndex}
  194. renderItem={(item) => (
  195. <PaginationItem
  196. component={Link}
  197. href={`${pashName}${query.q ? `?q=${query.q}` : ""}${
  198. item.page === 1 ? "" : `${query.q ? `&` : "?"}page=${item.page}`
  199. }`}
  200. {...item}
  201. />
  202. )}
  203. />
  204. ) : null}
  205. </main>
  206. );
  207. };
  208. export const getServerSideProps: GetServerSideProps<{
  209. fallback: Docs;
  210. }> = async ({ query }) => {
  211. const key = getKey(query);
  212. const data = await get(key);
  213. return {
  214. props: {
  215. fallback: {
  216. [key]: data,
  217. },
  218. },
  219. };
  220. };
  221. export default Genre;